/**
 * GraphData.java
 * colourmap
 * Copyright (C) 2010 Alan W.F. Boyd
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * You can contact the author via the google code web page for the project
 * <http://colourmap.googlecode.com>
 */
package com.googlecode.colourmap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;

import javax.swing.JOptionPane;
import javax.swing.ProgressMonitor;

import com.googlecode.colourmap.colours.UserDefinedColourScheme;
import com.googlecode.colourmap.interpolator.IInterpolator;
import com.googlecode.colourmap.interpolator.RadialInterpolator;

/**
 * @author Alan W.F. Boyd
 *
 */
public class GraphData
{
	/** The set of original data points from the data file */
	private HashMap<Point, Float> originalDataPoints = new HashMap<Point, Float>();
	
	/** The set of interpolated data points */
	private HashMap<Point, Float> allDataPoints = new HashMap<Point, Float>();
	
	/** Maximum X coordinate in the data */
	private float maxX = Float.MIN_VALUE;
	
	/** Maximum Y coordinate in the data */
	private float maxY = Float.MIN_VALUE;
	
	/** Maximum value in the data */
	private float maxDataPoint = Float.MIN_VALUE;
	
	/** Minimum X coordinate in the data */
	private float minX = Float.MAX_VALUE;
	
	/** Minimum Y coordinate in the data */
	private float minY = Float.MAX_VALUE;
	
	/** Minimum value in the data */
	private float minDataPoint = Float.MAX_VALUE;
	
	/** Monitor the handles the progress in processing work in the application */
	private ProgressMonitor monitor;
	
	/** Flag that indicates whether the data is being interpolated or not */
	private boolean interpolating = false;
	
	/** The resolution of the colour map image */
	private int resolution;
	
	/** The scale of the colour map image */
	private float scale;
	
	/**
	 * Extracts data from the data file
	 * @param fileName the data file
	 */
	public void loadData(String fileName)
	{
		File file = new File(fileName);
		FileReader fr = null;
		
		try
		{
			fr = new FileReader(file);
		}
		catch (FileNotFoundException e)
		{
			JOptionPane.showMessageDialog(null, "The file '" + fileName + "' could not be found.", "File Not Found", JOptionPane.ERROR_MESSAGE);
			System.exit(-1);
		}
			
		BufferedReader br = new BufferedReader(fr);
		
		String thisLine;
		try
		{
			while((thisLine = br.readLine()) != null)
			{
				String[] parts = thisLine.split(",");
				
				Float xCord = null;
				Float yCord = null;
				Float point = null;
				try
				{
					xCord = Float.parseFloat(parts[0]);
					yCord = Float.parseFloat(parts[1]);
					point = Float.parseFloat(parts[2]);
				}
				catch (NumberFormatException e)
				{
					JOptionPane.showMessageDialog(null, "The specified file is not a .csv file.", "Unknown Format", JOptionPane.ERROR_MESSAGE);
					System.exit(-1);
				}
				
				if (xCord == null || yCord == null || point == null)
					continue;
				
				if (xCord < minX)
					minX = xCord;
				if (xCord > maxX)
					maxX = xCord;
				if (yCord < minY)
					minY = yCord;
				if (yCord > maxY)
					maxY = yCord;
				if (point < minDataPoint)
					minDataPoint = point;
				if (point > maxDataPoint)
					maxDataPoint = point;
				
				Point thisCoordinate = new Point(xCord, yCord);
				originalDataPoints.put(thisCoordinate, point);
			}
		}
		catch (IOException e)
		{
			JOptionPane.showMessageDialog(null, "An unknown I/O error occured.", "I/O Error", JOptionPane.ERROR_MESSAGE);
			System.exit(-1);
		}
	}
	
	/**
	 * Adjusts the coordinates in the map to deal with the increased scale
	 */
	private void adjustForScale()
	{
		if (scale == 1)
			return;

		float newX, newY;
		
		HashMap<Point, Float> newPoints = new HashMap<Point, Float>();
		
		Iterator<Point> i = originalDataPoints.keySet().iterator();
		while (i.hasNext())
		{
			Point coord = i.next();
			Float value = originalDataPoints.get(coord);
			
			newX = (((coord.x - minX)) * scale) + minX;
			newY = (((coord.y - minY)) * scale) + minY;

			newPoints.put(new Point(newX, newY), value);
		}
		
		originalDataPoints = newPoints;
	}
	
	/**
	 * Uses the IInterpolator to determine what colour every point should be.
	 */
	private void interpolate()
	{
		interpolating = true;
		adjustForScale();
		
		IInterpolator interpolator = new RadialInterpolator();
		interpolator.setPoints(originalDataPoints);
		
		allDataPoints.putAll(originalDataPoints);
		
		int progress = 0;
		for (float i = minX; i < ((maxX-minX)*scale)+minX+resolution; i += resolution)
		{
			for (float j = minY; j < ((maxY-minY)*scale)+minY+resolution; j += resolution)
			{
				// Do not reinterpolate a point that is already known
				if (allDataPoints.containsKey(new Point(i, j)))
					continue;
				
				Point thisCoordinate = new Point(i, j);
				allDataPoints.put(thisCoordinate, interpolator.getValue(thisCoordinate));
			}
			
			progress = (int)((i-minX) * 99 / ((maxX-minX) * scale));
			
			monitor.setProgress(progress);
			monitor.setNote(progress + "%");
		}
		
		interpolating = false;
	}
	
	/**
	 * Determines the width of the colour map
	 * @return the width of the map
	 */
	public int getGraphWidth()
	{
		return (int) ((maxX - minX)*scale + Graph.BORDER*2);
	}
	
	/**
	 * Determines the height of the colour map
	 * @return the height of the map
	 */
	public int getGraphHeight()
	{
		return (int) ((maxY - minY)*scale + Graph.BORDER*2 + Graph.TITLEBAR);
	}
	
	/**
	 * Returns the maximum data value from the data set
	 * @return the maximum data value
	 */
	public float getMaxPoint()
	{
		return maxDataPoint;
	}
	
	/**
	 * Returns the minimum data value from the data set
	 * @return the minimum data value
	 */
	public float getMinPoint()
	{
		return minDataPoint;
	}
	
	/**
	 * Returns the minimum x coordinate from the data set
	 * @return the minimum x coordinate
	 */
	public float getMinX()
	{
		return minX;
	}
	
	/**
	 * Returns the minimum y coordinate from the data set
	 * @return the minimum y coordinate
	 */
	public float getMinY()
	{
		return minY;
	}
	
	/**
	 * Returns the resolution of the colour map
	 * @return the resolution
	 */
	public int getResolution()
	{
		return resolution;
	}
	
	/**
	 * Returns a map of coordinates for the original data points in the data file.
	 * @return a map of coordinates to data values
	 */
	public HashMap<Point, Float> getOriginalDataPoints()
	{
		return originalDataPoints;
	}
	
	/**
	 * Returns a map of coordinates for the full set of data points, including those that are interpolated
	 * @return a map of coordinates to data values
	 */
	public HashMap<Point, Float> getAllDataPoints()
	{
		if (!interpolating)
			return allDataPoints;

		return new HashMap<Point, Float>();
	}
	
	/**
	 * Renders the colour map, given a resolution of dots and a scale for the image
	 * @param resolution the resolution to use
	 * @param scale the scale to use
	 */
	public void drawMap(int resolution, float scale)
	{
		this.resolution = resolution;
		this.scale = scale;
		
		Graph graph = new Graph("Graph", this);
		monitor = new ProgressMonitor(graph, "Calculating Values", "0%", 0, 100);
		graph.setVisible(true);
		
		interpolate();
		
		graph.setColourScheme(new UserDefinedColourScheme());
		graph.setMonitor(monitor);
		graph.repaint();
		
		monitor.setNote("Drawing");
	}
}
